import * as ts from 'typescript';

import {
	createCombineArguments,
	createMembers,
	createPropertyTypeNode,
	createQuestionToken,
	createTypeLiteralNode,
	createTypeParameterDeclaration,
	literalFilters,
	skipIncludes,
} from './commom';
import { mainStateCompose } from './compose';
import {
	defaultFnSort,handleStrRef
} from './handleSchema';
import {
	combineParameters, findResRefClass
} from './inSearchFor';
import logger from './logger';

const { factory } = ts;
type ClauseKeyword = ts.SyntaxKind.ExtendsKeyword | ts.SyntaxKind.ImplementsKeyword;

/**
 * 用于存放响应对象中的引用类型信息；
 * 便于通过 deCallbackRef 函数，引入该类型的接口、类；
 */
let currentRef = '';
/**
 * 该函数用于在外部模块中访问 deCallbackRef 数组数据；
 * @param callback Function
 */
export function deCallbackRef (callback: Function) {
	if (currentRef) callback(currentRef);
}

export function createDecorator (content: string | string[]) {
	const decorators = [];
	for (const description of [].concat.call(null, content)) {
		decorators.push(factory.createDecorator(factory.createStringLiteral(description)));
	}
	return decorators;
}

export function createEnumDeclaration (aliasEnum: AliasEnum, keyword: number = ts.SyntaxKind.DeclareKeyword) {
	const members: ts.EnumMember[] = [];
	// 过滤数组中类型不一致的数据
	const validate = literalFilters();
	for (const iterator of aliasEnum.items || []) {
		let name = iterator.label || iterator;
		const initializer = iterator.value;

		if (typeof iterator === 'object') {
			if (!iterator.label) {
				logger.warn('Logger: createEnumDeclaration find label does not exist in items');
				continue;
			}
		}

		// 过滤数组中类型不一致的数据，直接跳过该数据的逻辑处理
		if (validate(iterator)) continue;
		name = factory.createNumericLiteral(String(iterator));

		members.push(
			factory.createEnumMember(name, initializer ? factory.createNumericLiteral(initializer) : initializer)
		);
	}
	return factory.createEnumDeclaration([factory.createModifier(keyword)], aliasEnum.name, members);
}

export function createHeritageClause (keyword: ClauseKeyword, content: string | string[]) {
	const withTypeArguments: ts.ExpressionWithTypeArguments[] = [];
	for (const title of [].concat.call([], content)) {
		withTypeArguments.push(
			ts.factory.createExpressionWithTypeArguments(ts.factory.createNumericLiteral(title), [])
		);
	}
	return ts.factory.createHeritageClause(keyword, withTypeArguments);
}

export function createInterfaceDeclaration (
	__ob__: Record<string, DefinitionSpec>,
	property: string,
	keyword?: ts.ModifierSyntaxKind,
	heritage?: string | string[]
) {
	const modifiers: ts.Modifier[] = [];
	if (keyword) modifiers.push(ts.factory.createModifier(keyword));

	const members: ts.TypeElement[] = createMembers(__ob__, property);
	const heritageClause = heritage ? [createHeritageClause(ts.SyntaxKind.ExtendsKeyword, heritage)] : [];

	property = handleStrRef(property);
	return factory.createInterfaceDeclaration(modifiers, property, undefined, heritageClause, members);
}

export function createTypeAliasDeclaration (
	__ob__: any,
	property: string,
	keyword?: ts.ModifierSyntaxKind,
	typeNode?: ts.TypeNode
) {
	const modifiers: ts.Modifier[] = [];
	if (keyword) modifiers.push(ts.factory.createModifier(keyword));

	typeNode = typeNode || ts.factory.createTypeLiteralNode(createMembers(__ob__, property));

	return factory.createTypeAliasDeclaration(modifiers, property, [], typeNode);
}

export function createModuleDeclaration (name: string, statements: ts.Statement[], declareModule?: boolean) {
	const modifiers: ts.Modifier[] = declareModule ? [ts.factory.createModifier(ts.SyntaxKind.DeclareKeyword)] : [];
	return factory.createModuleDeclaration(
		modifiers,
		factory.createIdentifier(name),
		factory.createModuleBlock(statements)
	);
}

export function createInnerParameterDeclaration (parameter: ParameterFace) {
	const {
		name, required, schema
	} = parameter;

	// 兼顾引用类型数据、与引用类型列表数据
	const type = parameter?.type || schema?.type;
	const $ref = schema?.$ref || schema?.items?.$ref;

	return createParameterDeclaration(name, required, createPropertyTypeNode(name, type, $ref));
}

export function createParameterDeclaration (name: string, required?: boolean, typeNode?: string | ts.TypeNode) {
	if (typeof typeNode === 'string') typeNode = factory.createTypeReferenceNode(typeNode);
	return factory.createParameterDeclaration([], undefined, name, createQuestionToken(required), typeNode);
}

function find (url: string, vals: Record<string, any>) {
	const match = url.match(/{.+}/g) ;
	return Object.keys(vals).length > (match ?? []).length;
}

/**
 * 该函数用于：生成网络请求-合并请求参数的函数-节点信息
 * example: API.delete(url, { a , b, c });
 * @param schema SchemaContentFace
 * @param keyword ts.ModifierSyntaxKind
 * @returns ts.FunctionDeclaration
 */
export function createFunctionDeclaration (
	schema: SchemaContentFace,
	keyword?: ts.ModifierSyntaxKind,
	confName = 'RequestConfig'
) {
	currentRef = '';
	const {
		parameters = [], response
	} = schema;
	let alphaQuery: string[] = ['config'];
	const listParameter: ts.ParameterDeclaration[] = [];

	// 结合~~对 required 进行排序
	// 避免未配置 required 的数据影响结果
	parameters.sort(defaultFnSort);

	// const dict = handleUrlParameters(schema.url, parameters);
	const compose = mainStateCompose(combineParameters(parameters, schema), schema);

	if (compose.type) {
		const literal = createTypeLiteralNode(compose.type);
		if (find(schema.url, compose.type)) {
			alphaQuery = ['state', 'config'];
		}
		listParameter.push(createParameterDeclaration('state', true, literal));
	}
	// config?: AxiosRequestConfig
	listParameter.push(createParameterDeclaration('config', false, confName));

	const API = factory.createIdentifier('API');
	const originalRef = handleStrRef(findResRefClass(response));

	//  用于在代码中导入引用类型接口
	if (originalRef && originalRef !== 'any') {
		currentRef = handleStrRef(originalRef);
	}

	const contentBlock = factory.createBlock([
		factory.createReturnStatement(
			factory.createCallExpression(
				factory.createPropertyAccessExpression(API, schema.method),
				[factory.createTypeReferenceNode('T')],
				createCombineArguments(compose.url, alphaQuery, compose.applyRef)
			)
		),
	]);

	const modifiers: ts.Modifier[] = [];
	if (keyword) modifiers.push(factory.createModifier(keyword));

	const typeParameters = createTypeParameterDeclaration([
		{
			name: 'T',
			default: createPropertyTypeNode(originalRef, undefined, originalRef)
		}
	]);

	return factory.createFunctionDeclaration(
		modifiers,
		undefined,
		schema.operationId,
		typeParameters,
		listParameter,
		undefined,
		contentBlock
	);
}

/**
 * 生成 .ts 引用逻辑代码
 * @param specifier string
 * @param fromUrl string
 * @param imports string[]
 * @returns
 */
export function createImportDeclaration (fromUrl: string, imports: DeclareNameProps, specifier?: string) {
	let name: ts.Identifier | undefined;

	const elements = imports.map((element) => {
		if (typeof element === 'string') {
			return factory.createImportSpecifier(false, undefined, factory.createIdentifier(element));
		} else {
			return factory.createImportSpecifier(
				false,
				factory.createIdentifier(element.name),
				factory.createIdentifier(element.alias)
			);
		}
	});

	const bindings = factory.createNamedImports(elements);

	if (specifier) name = factory.createIdentifier(specifier);

	const clause = factory.createImportClause(false, name, bindings);

	return factory.createImportDeclaration([], clause, factory.createStringLiteral(fromUrl));
}

export function createImportEqualsDeclaration (declareName: string, fromUrl: string) {
	return factory.createImportEqualsDeclaration(
		[],
		false,
		declareName,
		factory.createExternalModuleReference(factory.createStringLiteral(fromUrl))
	);
}

export function createJSDocComment (JSDocText: string) {
	return factory.createJSDocComment(JSDocText, []);
}

export function createJSDocCommentFunction (JSDocText: string, parameters: ParameterFace[]) {
	const JSDocTags: ts.JSDocTag[] = [];
	for (const parameter of parameters || []) {
		const {
			name, description
		} = parameter;
		if (skipIncludes(parameter)) continue;

		JSDocTags.push(
			factory.createJSDocParameterTag(
				undefined,
				factory.createIdentifier(name),
				false,
				undefined,
				undefined,
				description
			)
		);
	}

	return factory.createJSDocComment(
		JSDocText,
		JSDocTags.concat(factory.createJSDocReturnTag(undefined, undefined, undefined))
	);
}
